Lambda表达式 - 应用示例

作者:陈广
日期:2018-2-15


上一篇文章简单演示了委托、匿名函数、Lambda表达式。仅仅这样肯定是不够的,还需要更多了解它们的示例,才能在阅读相关代码时不会那么吃力。所以这篇文章就举一些例子帮助读者作进一步的了解。将来学习.NET Core时遇到典型应用场景时,也会追加到这篇文章内。

List<T>.ForEach

先看例子:

using System;
using System.Collections.Generic;

namespace lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> nums = new List<int>() { 1, 2, 3, 4, 5, 6 };
            nums.ForEach(number => Console.Write(number + " "));
        }
    }
}

运行结果:1 2 3 4 5 6

在此例中,我们先声明了一个泛型集合List,指定了其中只能装载整数类型,然后将1到6装入此集合。接下来,调用List<T>ForEach方法遍历所有元素并打印。

此例关键为ForEach是如何动作的。先来看ForEach函数原型:

public void ForEach(Action<T> action)

ForEach函数需传递一个Action<T>参数,上篇文章中我们已经知道,Action<T>为带参无返回值委托。它的作用是提供如何处理List<T>集合中的每一个元素的方法。此例中,我们使用的委托为:

number => Console.Write(number + " ")

它只是简单地打印出当前元素,并在其后添加一个空格。

当然,此时我们无法体会到使用委托的优势,但当有多个处理方法,或者需要将此动作作为参数进行传递时,就能体现优势了。修改程序:

static Action<int> print1 = number => Console.Write(number + " ");
static Action<int> print2 = number => Console.Write(number + "-");
static void Main(string[] args)
{
    List<int> nums = new List<int>() { 1, 2, 3, 4, 5, 6 };
    nums.ForEach(print1);
    Console.WriteLine();
    nums.ForEach(print2);
}

运行结果:

1 2 3 4 5 6 
1-2-3-4-5-6-

假设针对这个List<T>里的元素有多种处理方法,那么我们只需针对每一种处理方法声明一个委托。在调用时根据你需要的处理方法调用相应委托即可。此例我们为简单起见,只是打印每个元素,第一种处理方法是使用空格分隔,第二种处理方法是使用减号分隔。最后两种方式都打印出来。

List<T>里的元素我们称为内容,而处理方法Action<T>称为动作,ForEach()方法则使得我们有机会将内容和动作分离。

Comparison<T>

下面我们把上面的例子稍作修改,使用List<T>的``sort()`方法进行排序:

static Action<int> print = number => Console.Write(number + " ");
static void Main(string[] args)
{
    List<int> nums = new List<int>() { 3, 1, 6, 5, 2, 4 };
    nums.Sort();
    nums.ForEach(print);
}

运行结果:1 2 3 4 5 6

自定义类的对比

这样排序不会有问题,但如果List<T>里存放的是一个自定义对象就不一定了。我们修改程序,在List<T>内存放自定义对象:

class student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
static Action<student> print = stu => Console.WriteLine($"学号={stu.ID},姓名={stu.Name},出生日期={stu.Birthday}");
static void Main(string[] args)
{
    List<student> stus = new List<student>
    {
        new student{ID=3,Name="张三",Birthday=DateTime.Parse("1990-5-1") },
        new student{ID=2,Name="李四",Birthday=DateTime.Parse("1990-1-1") },
        new student{ID=4,Name="王五",Birthday=DateTime.Parse("1990-2-1") },
        new student{ID=1,Name="马六",Birthday=DateTime.Parse("1990-9-1") }
    };
    stus.Sort()
    stus.ForEach(print);
}

运行程序,抛出异常。这是因为Sort()方法需要有两个元素对比的规则。之前集合内存放的是整数,整数本身是已经有对比规则的,但我们自定义的类并无实现对比规则,所以在调用Sort()时出错,它不知道如何对比两个元素。

Sort()方法中加入对比规则有三种方式:

  1. 第一种是让student类实现IComparable<T>接口
  2. 实现一个IComparerIComparer<T>接口,作为参数传递给Sort()方法作为比较依据
  3. 实现一个Comparison<T>委托,作为参数传递给Sort()方法作为比较依据

前两种方法不在本文讲述范围,第二种方法在我的《数据结构 C#语言描述》中有相关例子。我们这里只讲解第三种方法的实现。

使用Comparison委托进行排序

ComparisonAction一样,都是微软内置的委托对象。我们先到MSDN中查看Comparison<T>委托原型:

public delegate int Comparison<in T>(T x, T y);

下面更改之前的程序,使之正常运行:

class student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
static Action<student> print = stu => Console.WriteLine($"学号={stu.ID},姓名={stu.Name},出生日期={stu.Birthday}");
//使用ID进行排序的委托
static Comparison<student> comparyByID = (stu1, stu2) => stu1.ID.CompareTo(stu2.ID);
//使用Name进行排序的委托
static Comparison<student> comparyByName = (stu1, stu2) => stu1.Name.CompareTo(stu2.Name);
//使用Birthday进行排序的委托
static Comparison<student> comparyByBirthday = (stu1, stu2) => stu1.Birthday.CompareTo(stu2.Birthday);
static void Main(string[] args)
{
    List<student> stus = new List<student>
    {
        new student{ID=3,Name="张三",Birthday=DateTime.Parse("1990-5-1") },
        new student{ID=2,Name="李四",Birthday=DateTime.Parse("1990-1-1") },
        new student{ID=4,Name="王五",Birthday=DateTime.Parse("1990-2-1") },
        new student{ID=1,Name="马六",Birthday=DateTime.Parse("1990-9-1") }
    };
    stus.Sort(comparyByID); //使用ID进行排序
    stus.ForEach(print);
    Console.WriteLine("--------------------------------------------");
    stus.Sort(comparyByName);//使用Name进行排序
    stus.ForEach(print);
    Console.WriteLine("--------------------------------------------");
    stus.Sort(comparyByBirthday);//使用Birthday进行排序
    stus.ForEach(print);
    Console.ReadLine();
}

运行结果:

学号=1,姓名=马六,出生日期=1990/9/1 0:00:00
学号=2,姓名=李四,出生日期=1990/1/1 0:00:00
学号=3,姓名=张三,出生日期=1990/5/1 0:00:00
学号=4,姓名=王五,出生日期=1990/2/1 0:00:00
--------------------------------------------
学号=2,姓名=李四,出生日期=1990/1/1 0:00:00
学号=1,姓名=马六,出生日期=1990/9/1 0:00:00
学号=4,姓名=王五,出生日期=1990/2/1 0:00:00
学号=3,姓名=张三,出生日期=1990/5/1 0:00:00
--------------------------------------------
学号=2,姓名=李四,出生日期=1990/1/1 0:00:00
学号=4,姓名=王五,出生日期=1990/2/1 0:00:00
学号=3,姓名=张三,出生日期=1990/5/1 0:00:00
学号=1,姓名=马六,出生日期=1990/9/1 0:00:00

这里,我们通过在Sort()中使用不同的委托,可以实现按照不同的字段进行排序,从而得到不同的排序结果。